﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Xml;
using System.Xml.Serialization;
using EncryptionLib.Util;
using System.Diagnostics;
using System.Text;

namespace EncryptionLib.Users
{
    public class UserManagerXML : IUserManager
    {
        private List<UserData> m_users = new List<UserData>();

        public string DataFile
        {
            get;
            protected set;
        }

        public int GetCount()
        {
            return m_users.Count;
        }

        public UserData GetUser(int index)
        {
            if (index >= 0 && index < GetCount())
                return m_users[index];
            return null;
        }

        public UserData GetUser(string sUserID, Func<string, UserData, bool> fnChecker)
        {
            foreach (UserData userData in m_users)
            {
                if (fnChecker(sUserID, userData))
                {
                    return userData;
                }
            }
            return null;
        }

        public UserData GetUserByEmail(string sEmail)
        {
            Func<string, UserData, bool> checker = delegate(string s, UserData userData)
            {
                return s.ToLower().CompareTo(userData.Email.ToLower()) == 0;
            };
            return GetUser(sEmail, checker);
        }

        public bool Register(UserData userData)
        {
            UserData user = FindUser(userData);
            if(user == null)
            {
                m_users.Add(userData);
                return true;
            }
            return false;
        }

        public bool UpdateUserData(UserData userData)
        {
            UserData user = FindUser(userData);
            if (user != null)
            {
                m_users.Remove(user);
                m_users.Add(userData);
                return true;
            }
            return false;
        }

        public UserData Login(string sUserID, string sPassphrase, Func<string, UserData, bool> fnLoginChecker)
        {
           foreach(UserData userData in m_users)
           {
               if(fnLoginChecker(sUserID, userData))
               {
                   if (userData.Passphrase.Verify(SerializerHelper.String2Bytes(sPassphrase)))
                       return userData;
               }
           }
           return null;
        }

        public UserData LoginByEmail(string sEmail, string sPassphrase)
        {
            Func<string, UserData, bool> loginCriteria = delegate(string s, UserData userData)
            {
                return s.ToLower().CompareTo(userData.Email.ToLower()) == 0;
            };
            return Login(sEmail, sPassphrase, loginCriteria);
        }

        public static bool ExportUserData(UserData userData, string sOutputFile)
        {
            if (userData != null)
            {
                XmlSerializer xml = new XmlSerializer(userData.GetType());
                FileStream fs = new FileStream(sOutputFile, FileMode.Create, FileAccess.Write);
                xml.Serialize(fs, userData);
                fs.Close();
                return true;
            }
            return false;
        }

        public static UserData ImportUserData(string sInputFile)
        {
            FileStream fs = new FileStream(sInputFile, FileMode.Open, FileAccess.Read);
            UserData userRet = new UserData();
            XmlSerializer xml = new XmlSerializer(userRet.GetType());
            if(xml.CanDeserialize(XmlReader.Create(fs)))
            {
                fs.Seek(0, SeekOrigin.Begin);
                userRet = (UserData)xml.Deserialize(fs);
            }
            else
            {
                userRet = null;
            }
            fs.Close();
            return userRet;
        }

        public void Clear()
        {
            m_users.Clear();
        }

        public void SaveData()
        {
            SaveData(DataFile);
        }

        public void SaveData(string sXmlFile)
        {
            XmlSerializer x = new XmlSerializer(m_users.GetType());
            using (MemoryStream streamRealData = new MemoryStream())
            {
                x.Serialize(streamRealData, m_users);

                using(FileStream streamData = new FileStream(sXmlFile, FileMode.Create, FileAccess.Write))
                {
                    StreamEncrypt(streamRealData, streamData);
                    streamData.Close();
                }
            }
        }

        public UserManagerXML(string sXmlDataFile)
        {
            DataFile = sXmlDataFile;
            FileInfo fInfo;

            try
            {
                fInfo = new FileInfo(sXmlDataFile);
            }
            catch(Exception /*ex*/)
            {
                return;
            }

            if(File.Exists(DataFile) && fInfo.Length > 0)
            {
                FileStream streamEncryptedData = null;
                MemoryStream streamData = null;
                
                try
                {
                    streamEncryptedData = new
                        FileStream(sXmlDataFile, FileMode.Open, FileAccess.Read);
                    streamData = new MemoryStream();
                    StreamDecrypt(streamEncryptedData, streamData);

                    XmlSerializer x = new XmlSerializer(m_users.GetType());
                    streamData.Seek(0, SeekOrigin.Begin);

                    if (x.CanDeserialize(XmlReader.Create(streamData)))
                    {
                        streamData.Seek(0, SeekOrigin.Begin);
                        m_users = (List<UserData>)x.Deserialize(streamData);
                    }
                }
                catch(Exception ex)
                {
                    Debug.Write("Error when deserializing: ");
                    Debug.WriteLine(ex.Message);
                }
                finally
                {
                    if (streamEncryptedData != null)
                        streamEncryptedData.Close();
                    if (streamData != null)
                        streamData.Dispose();
                }
            }
        }

        public UserManagerXML() : this(Path.GetTempFileName())
        {
        }

        private UserData FindUser(UserData userData)
        {
            Predicate<UserData> finder = delegate(UserData u)
            {
                return string.Compare(u.Email, userData.Email, true) == 0;
            };
            return m_users.Find(finder);
        }

        private void CreatePassAndIV(ref byte[] arrPass, ref byte[] arrIV)
        {
            int iLen = SymmetricAlgo.GetDESKeyLen();
            if (iLen % 8 != 0)
                return;

            iLen /= 8;
            arrPass = new byte[iLen];
            arrIV = new byte[iLen];

            byte i, j;
            for(i = byte.MaxValue, j = 0; j < iLen; ++j)
            {
                arrIV[j] = arrPass[j] = (byte)(i - j);
            }
        }
        private void StreamEncrypt(Stream streamInput, Stream streamOutput)
        {
            byte[] arrPass = null;
            byte[] arrIV = null;
            CreatePassAndIV(ref arrPass, ref arrIV);
            SymmetricAlgo.DESEncrypt(streamInput, streamOutput,
                arrPass, arrIV, System.Security.Cryptography.CipherMode.CBC,
                System.Security.Cryptography.PaddingMode.PKCS7);
        }
        private void StreamDecrypt(Stream streamInput, Stream streamOutput)
        {
            byte[] arrPass = null;
            byte[] arrIV = null;
            CreatePassAndIV(ref arrPass, ref arrIV);
            SymmetricAlgo.DESDecrypt(streamInput, streamOutput,
                arrPass, arrIV, System.Security.Cryptography.CipherMode.CBC,
                System.Security.Cryptography.PaddingMode.PKCS7);
        }
    }
}
